<?php
/*
Version 0.1
SEMF - Web Desiging Framework for Sport Event Managment
Copyright (C) 2006, SEMF Developers

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

include_once("token.php");
include_once("function.php");

class tokenizer
{
	private $tokens;
	private $script;
	private $script_len;
	
	private $current_index;
	
	private $count;
	
	private $line_no;
	private $col_no;
	
	private $cols_count;
	private $description;
	
	private $error_msg;
	private $project_home;
	
	private $foreign;
	
	public function __construct($script, $project_home, $description)
	{
		$this->tokens = array();
		$this->script = $script;
		$this->script_len = strlen($script);
		
		$this->current_index = 0;
		$this->count = 0;
		
		$this->cols_count = array();
		
		$this->line_no = 1;
		$this->col_no = 0;
		
		$this->description = $description;
		$this->error_msg = "";
		
		$this->project_home = $project_home;
		$this->foreign = array();
		
		$this->load_module_info();
		$this->load_functs_info();
		
		//print($script."\n");
	}
	
	public function tokenize()
	{
		//echo $this->script;
		$next_token = "";
		$type = token::$NOT_A_TOKEN;
		$line_no = 0;
		$cel_no = 0;
		
	 	while( ($c = $this->get_char()) != NULL )
		{
			//echo $c;
			//loop always start at the start of a token..
			$next_token = "";
			$type = token::$NOT_A_TOKEN;
			
			if( ctype_alnum ($c) || $c == "_") 
			//straightaway alpha numaric character, it is a constant
			{
				//$string = $string.$c;
				$this->unget_char();
				while( ($c = $this->get_char()) != NULL &&
							(ctype_alnum($c) || $c == "_") )
				{
					$next_token = $next_token.$c;
				}
				$type = NULL;
				switch($next_token)
				{
					case "if":
						$type = token::$IF;
						break;
					case "else":
						$type = token::$ELSE;
						//chekcing for else if
						//ignore the wildspaces
						while( ($c = $this->get_char()) != NULL &&
							($c == ' ' || $c == '\n' || $c == '\r' ))
						{
						}
						$forsee = 0;
						if($c == 'i')
						{
							$forsee ++;
							if(($c = $this->get_char()) == 'f')
							{
								$forsee ++;
								if(!ctype_alnum($c = $this->get_char()))
								{
									$forsee ++;
									//we come there, this is elseif statement
									$type = token::$ELSEIF;
									$next_token = "elseif";
									//$this->get_char();
								}
							}
						}
						if( $forsee != 3)
						{
							//just undo the read of next for characters.
							for( $k = 0; $k < $forsee; $k ++ )
							{
								$this->unget_char();
							}
						}
						break;
						
					case "elseif":
						$type = token::$ELSEIF;
						break;
					case "null":
					case "NULL":
						$type = token::$NULL;
						break;
					case "this":
						$type = token::$THIS;
						break;
					/*case "this_module":
						$type = token::$THIS_MODULE;
						break;
					case "this_user":
						$type = token::$THIS_USER;
						break;
					case "this_time":
						$type = token::$THIS_TIME;
						break; */ // these are moved to system variable..
						
					case "select":
					case "SELECT":
						$type = token::$SELECT;
						break;
					case "from":
					case "FROM":
						$type = token::$FROM;
						break;
					case "where":
					case "WHERE":
						$type = token::$WHERE;
						break;
					case "decl":
					case "DECL":
					case "declare":
					case "DECLARE":
						$type = token::$DECL;
						break;
					case "new":
					case "NEW":
						$type = token::$NEW;
						break;
					case "delete":
					case "DELETE":
						$type = token::$DELETE;
						break;
						

					default:
						$type = NULL;
						//$type = token::$CONSTANT;
						//constants can be arbitary long..
						// untill alpha numeric or space is not detected.
				}
				if( $type == NULL) // not a token..
				{
					//echo "yea it is null";
					$this->unget_char();
					$addition_value = "";
					while( ($c = $this->get_char()) != NULL && 
					       (ctype_alnum($c) || $c == " " || $c == "_" || $c == "/")
					     )
					{
						$addition_value = $addition_value.$c;
					}
					if( trim($addition_value) != "" )
					{
						$next_token = $next_token.$addition_value;
					}
					if(is_numeric($next_token))
					{
						$type = token::$NUMBER;
					}
					else
					{
						if($this->foreign[$next_token] == 1)
						{
							$type = token::$CONSTANT;
						}
						else
						{
							$type = NULL;
						}
						//$type = token::$CONSTANT;
					}
				}
				$this->unget_char();
			}
			elseif( $c == "'" || $c == "\"" )
			//quotes, it is a constant
			{
				$quotechar = $c;
				$earlychar = $c;
				while( (($c = $this->get_char()) != $quotechar && 
				       $c != "\\") //if the quote then we know the end of the quote
					   && $c != NULL )
				{
					// you can type any resevered word here without any problem
					$next_token = $next_token.$c;
					$earlychar = $c;
				}
				if( $c == "\\")
				{
					$this->get_char(); //just escape the next char..
				}
				// opening & closing quote also included..
				$type = token::$CONSTANT;
			}
			elseif( $c == "$" )
			//$, it is a variable
			{
				$next_token = "";
				while( ($c = $this->get_char()) != NULL &&
							ctype_alnum($c) || $c == "_" )
				{
					$next_token = $next_token.$c;
				}				
				$type = token::$VARIABLE;
				$this->unget_char();
			}
			elseif( $c == "@" )
			//$, it is a table variable
			{
				$next_token = "";
				while( ($c = $this->get_char()) != NULL &&
							ctype_alnum($c) || $c == "_")
				{
					$next_token = $next_token.$c;
				}				
				$type = token::$DATA_VAR;
				$this->unget_char();
			}
			elseif( $c == "%" )
			//$, it is a system variable
			{
				$next_token = "";
				while( ($c = $this->get_char()) != NULL &&
							ctype_alnum($c) || $c == "_")
				{
					$next_token = $next_token.$c;
				}				
				$type = token::$SYS_VAR;
				$this->unget_char();
			}
			elseif( $c == ";")
			//; , it is a SEMI_COL
			{
				$next_token = ";";
				$type = token::$SEMI_COL;
			}
			elseif( $c == "=")
			{
				if( ($c = $this->get_char()) == "=") //for concecetive '=' s
				{
					$next_token = "==";
					$type = token::$EQUILITY;
				}
				else
				{
					$this->unget_char();
					$next_token = "=";
					$type = token::$EQUAL;
				}
			}
			elseif( $c == "!")
			{
				if( ($c = $this->get_char()) == "=") //for concecetive '=' s
				{
					$next_token = "!=";
					$type = token::$NOT_EQUILITY;
				}
				else
				{
					$this->unget_char();
					$next_token = "!";
					$type = token::$NOT_EXPR;
				}
			}
			elseif( $c == "<")
			{
				if( ($c = $this->get_char()) == "=") //for concecetive '=' s
				{
					$next_token = "<=";
					$type = token::$LESS_THAN_EQUAL;
				}
				else
				{
					$this->unget_char();
					$next_token = "<";
					$type = token::$LESS_THAN;
				}
			}
			elseif( $c == ">")
			{
				if( ($c = $this->get_char()) == "=") //for concecetive '=' s
				{
					$next_token = ">=";
					$type = token::$GREATER_THAN_EQUAL;
				}
				else
				{
					$this->unget_char();
					$next_token = ">";
					$type = token::$GREATER_THAN;
				}
			}
			elseif( $c == "|")
			{
				$next_token = "||";
				if( ($c = $this->get_char()) != "|") //concecetive '|'s are allowed
				{
					$this->unget_char();
					$next_token = "|";
				}
				$type = token::$OR;
			}
			elseif( $c == "&")
			{
				$next_token = "&&";
				if( ($c = $this->get_char()) != "&") //concecetive '|'s are allowed
				{
					$this->unget_char();
					$next_token = "&";
				}
				$type = token::$AND;
			}
			elseif( $c == ",")
			{
				$next_token = ",";
				$type = token::$COMMA;
			}
			elseif( $c == "{")
			{
				$next_token = "{";
				$type = token::$OPEN_BLOCK;
			}
			elseif( $c == "}")
			{
				$next_token = "}";
				$type = token::$CLOSE_BLOCK;
			}
			elseif( $c == ".")
			{
				$next_token = ".";
				$type = token::$DOT;
			}
			elseif( $c == "(")
			{
				$next_token = "(";
				$type = token::$OPEN_BRACKET;
			}
			elseif( $c == ")")
			{
				$next_token = ")";
				$type = token::$CLOSE_BRACKET;
			}
			elseif( $c == "+") //for addition
			{
				if( ($c = $this->get_char()) == "+") 
				{
					$next_token = "++";
					$type = token::$ADD_ADD;
				}
				else if( $c == "=")
				{
					$next_token = "+=";
					$type = token::$ADD_EQUAL;
				}
				else
				{
					$next_token = "+";
					$type = token::$ADD;
					$this->unget_char();
				}
			}
			elseif( $c == "-") //for substraction
			{
				if( ($c = $this->get_char()) == "-") 
				{
					$next_token = "--";
					$type = token::$ADD_SUB;
				}
				else if( $c == "=")
				{
					$next_token = "-=";
					$type = token::$SUB_EQUAL;
				}
				else
				{
					$next_token = "-";
					$type = token::$SUB;
					$this->unget_char();
				}
			}
			elseif( $c == "*") //for multiplication
			{
				if( $c == "=")
				{
					$next_token = "*=";
					$type = token::$MUL_EQUAL;
				}
				else
				{
					$next_token = "*";
					$type = token::$MUL;
					$this->unget_char();
				}
			}
			elseif( $c == "/") //for multiplication
			{
				if( $c == "=")
				{
					$next_token = "/=";
					$type = token::$DIV_EQUAL;
				}
				else
				{
					$next_token = "/";
					$type = token::$DIV;
					$this->unget_char();
				}
			}
			
			if($type == NULL)//invalid token..
			{
				$this->report_error("Unknown token: ".$next_token);
				break;
			}
			
			// now just add the new token
			if( $type != token::$NOT_A_TOKEN )
			{
				$this->add_token( $type, $next_token, $this->line_no, $this->col_no);
			}
		}
		flush();
		//print_r($this->tokens);
		return $this->tokens;
	}
	
	private function get_char()
	{
		$c = $this->get_char_real();
		if($c == "/")
		{
			$c = $this->get_char_real();
			if($c == "/") //one line comment
			{
				while($c != "\n" && $c != NULL)
				{
					$c = $this->get_char_real();
				}
				$c = $this->get_char_real();
				return $c; //just after the $c..
			}
			elseif($c == "*") //multi line commment
			{
				$continue = true;
				$c = $this->get_char_real();
				while($continue)
				{
					while($c != "*" && $c != NULL)
					{
						$c = $this->get_char_real();
					}
					$c = $this->get_char_real();
					// now one ending "*" is found..
					if($c == "/") // but the next char should be /
					{
						$c = $this->get_char_real();
						return $c;
					}
					if($c == NULL)
					{
						$this->report_error("The multiline comment is not ended ");
						return NULL;
					}
					//else the comment would be continue..
				}
			}
			else
			{
				//sorry about worrying about a comment
				$this->unget_char();
				$c =  "/";
				return $c;
			}
		}
		return $c;
	}
	
	private function get_char_real()
	{
		if( $this->current_index < $this->script_len)
		{
			$c = substr($this->script, $this->current_index ++, 1);
			
			if ( $c == '\n' )
			{
				$this->cols_count[$this->line_no] = $this->col_no;
				$this->line_no ++;
				$this->col_no = 0;
			}
			$this->col_no ++;
			return $c;
		}
		//for the consistancey
		$this->current_index ++ ;
		return NULL;
	}
	
	public function report_error($error_msg)
	{
		if( $this->error_msg == "" ) // the first one
		{
			$this->error_msg .= $this->description;
			echo $this->description;
		}
		$error_msg = "TOKEN_ERROR:\n".
						$error_msg."\n<hr/>";
		$this->error_msg .= $error_msg;
		echo $error_msg;
	}
		
	private function unget_char()
	{
		$this->current_index --;
		
		if ($this->col_no == 0)
		{
			$this->line_no --;
			$this->col_no = $this->cols_count[$this->line_no];
		}
		$this->col_no --;
	}
	
	private function add_token($type, $value, $line_no, $col_no)
	{
	
		//echo $value.",".$this->line_no.",".$this->col_no."\n";
		$this->tokens[$this->count++] = new token($type, $value, $line_no, $col_no);
	}
	
	private function load_module_info()
	{
		//echo "project home:".$this->project_home."\n";
		$files = array();
		$d = dir($this->project_home);
		// Loop through all of the files:
		$i = 0;
		while (false !== ($file = $d->read()))
		{
			// If the file is not this file, and does not start with a '.' or '~'
			// and does not end in LCK, then store it for later display:
			if ( ($file{0} != '.') &&
				 ($file{0} != '~') &&
				 (substr($file, -3) != 'php') &&
				 (substr($file, -3) != 'xml')  )
			{
				if($file == "store")
				{
					continue;
				}
				$files[$i++] = $file;
			}
		}
		
		$foreign = array();
			
		foreach($files as $file)
		{
			$module_name = $file;
			
			//load from module.xml
			//store the module names..
			$foreign[$module_name] = 1;
			
			$module_xml = $this->project_home.$file."/"."module.xml";
			
			$doc = new DOMDocument();
			$doc->load($module_xml);
			
			if($doc == NULL || $doc->documentElement == NULL)
			{
				continue;
			}
			$table_elements = $doc->documentElement->childNodes;
			
			foreach ($table_elements as $table_element)
			{
				if($table_element->nodeType != XML_ELEMENT_NODE)
				{
					continue;
				}
				$table_name = $table_element->getAttribute("name");
				
				//store the table name
				$foreign[$table_name] = 1;
				
				$field_elements = $table_element->childNodes;
				
				foreach($field_elements as $field_element)
				{
					if($field_element->nodeType != XML_ELEMENT_NODE)
					{
						continue;
					}
					$field_name = $field_element->getAttribute("name");
					$field_type = $field_element->getAttribute("type");
					
					$is_array = $field_element->getAttribute("isarray");
					
					//store field name
					$foreign[$field_name] = 1;
					
					$istable = substr($field_type, 0, 5);
					if(trim(strtolower($istable)) == "table")
					{
						$field_type = substr($field_type, 6);
						$associated_table_name = str_replace(".", "_", $field_type);
						
						//store associated ones.., right now it is abbonded..
						$foreign[$associated_table_name] = "";
					}
			
					if($is_array)
					{
						$table_info[$table_name][$field_name]["array"] = true;
					}
				}
			}
			//load from pages
			$module_home = $this->project_home.$file."/";
			$pages = array();
			$d = dir($module_home);
			// Loop through all of the files:
			$i = 0;
			while (false !== ($page = $d->read()))
			{
				// If the file is not this file, and does not start with a '.' or '~'
				// and does not end in LCK, then store it for later display:
				if ( ($page{0} != '.') &&
					 ($page{0} != '~') &&
					 (substr($page, -3) == 'xml') )
				{
					if($page == "module.xml")
					{
						continue;
					}		
					$pos = strpos($page, ".xml");
					$pages[$i] = substr($page, 0, $pos);
					$foreign[$pages[$i++]] = 1;
				}
			}
		}
		$foreign["id"] = 1; // in a case it is forgotton..
		$foreign["extra"] = 1; 
		$this->foreign = array_merge($this->foreign, $foreign);
	}	
	
	function load_functs_info()
	{
		$functs = funct::$functs;
		$funct_names = array_keys($functs);
		$foreign = array();
		
		foreach($funct_names as $funct_name)
		{
			$foreign[$funct_name] = 1;
		}
		$this->foreign = array_merge($this->foreign, $foreign);
	}
}
?>